%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Helvetica', 'primaryColor': '#e8f4f8', 'primaryTextColor': '#1f2d3d', 'primaryBorderColor': '#355c7d', 'lineColor': '#355c7d', 'secondaryColor': '#d6eef5', 'tertiaryColor': '#fff3cd', 'background': '#ffffff', 'mainBkg': '#e8f4f8', 'secondBkg': '#d6eef5', 'tertiaryBkg': '#fff3cd', 'clusterBkg': '#f9fbfd', 'clusterBorder': '#355c7d', 'edgeLabelBackground': '#ffffff' }}}%%
%%| fig-cap: "A class bundles data members and member functions into one user-defined type"
%%| fig-width: 6
%%| fig-height: 3
classDiagram
class Point {
- double x
- double y
+ Move(dx, dy)
}
W2. C++ Classes Without OOP, Constructors and Destructors, Declarations and Initialization, Type Conversions, Operator and Conversion Functions
1. Summary
1.1 Introduction to C++ Classes
A class in C++ is a user-defined compound type that allows grouping data (variables) and functions that operate on that data together. While classes form the foundation of object-oriented programming, they can be used effectively without OOP principles.
There are three main perspectives on C++ classes:
- As a compound type: A class groups multiple variables of different types together
- As a user-defined type: A class can behave similarly to predefined types like
intordouble - As a basis for OOP: Classes support encapsulation, inheritance, and polymorphism (discussed in later lectures)
1.1.1 Basic Class Structure
A class definition specifies both the structure (data members) and operations (member functions) for objects of that type:
class Point {
double x;
double y;
};This simple class defines a Point type with two data members: x and y coordinates.
1.2 Access Control: Public and Private
C++ provides access specifiers to control which parts of a class are accessible from outside the class:
private: Members marked as private are only accessible from within the class itself. This is the default access level for class members.public: Members marked as public can be accessed by any code that has access to an object of that class.
This distinction supports encapsulation - hiding implementation details while exposing a controlled interface:
class Point {
private:
double x, y; // Implementation (hidden)
public:
void Move(double dx, double dy) { // Interface (visible)
x += dx;
y += dy;
}
};The usual pattern is to make data members private and provide controlled access through public member functions. This allows:
- Validation of data before it’s stored
- Flexibility to change internal representation without affecting users
- Read-only access to data (getters without setters)
1.2.1 Accessing Class Members
For objects declared directly, use the dot notation:
Point p1;
p1.Move(0.5, 0.5); // Call member functionFor pointers to objects, use the arrow notation:
Point* p = new Point();
p->Move(0.5, 0.5); // Call member function via pointer1.3 Scope and Lifetime
Before diving deeper into classes, it’s crucial to understand two fundamental concepts: scope and lifetime.
Scope determines where in your code a variable name is visible and can be used:
void function1() {
int x = 5; // x's scope: inside function1 only
// Can use x here
} // End of x's scope
void function2() {
// Cannot use x here - it's out of scope
int y = 10; // y's scope: inside function2 only
}Lifetime determines when an object exists in memory (from creation to destruction):
void example() {
int x = 5; // x's lifetime begins here
{
int y = 10; // y's lifetime begins here
// Both x and y exist
} // y's lifetime ends - y is destroyed
// Only x exists here
} // x's lifetime ends - x is destroyedKey insight: Scope is a compile-time concept (where names are visible), while lifetime is a runtime concept (when objects actually exist in memory).
%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Helvetica', 'primaryColor': '#e8f4f8', 'primaryTextColor': '#1f2d3d', 'primaryBorderColor': '#355c7d', 'lineColor': '#355c7d', 'secondaryColor': '#d6eef5', 'tertiaryColor': '#fff3cd', 'background': '#ffffff', 'mainBkg': '#e8f4f8', 'secondBkg': '#d6eef5', 'tertiaryBkg': '#fff3cd', 'clusterBkg': '#f9fbfd', 'clusterBorder': '#355c7d', 'edgeLabelBackground': '#ffffff' }}}%%
%%| fig-cap: "Scope and lifetime are different: a name can be visible in one region while the object exists only for a specific interval"
%%| fig-width: 6.2
%%| fig-height: 3.2
flowchart TB
Scope["Scope<br/>where the name is visible"]
Lifetime["Lifetime<br/>when the object exists in memory"]
Scope --> Lifetime
1.4 The this Pointer
Inside member functions, there’s a special hidden pointer called this that points to the object on which the member function was called.
class Point {
double x, y;
public:
void setX(double x) {
this->x = x; // this->x is the member, x is the parameter
}
Point& getReference() {
return *this; // Returns reference to the current object
}
};
Point p;
p.setX(5.0); // Inside setX, 'this' points to pWhy this is useful:
- Disambiguate names: When parameter names match member names
- Return the object itself: For method chaining (
obj.method1().method2()) - Pass object to other functions:
someFunction(this)orsomeFunction(*this) - Check for self-assignment:
if (this != &other) { ... }
Type of this: For a class C, inside a member function, this has type C* (pointer to C). For const member functions, it has type const C*.
%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Helvetica', 'primaryColor': '#e8f4f8', 'primaryTextColor': '#1f2d3d', 'primaryBorderColor': '#355c7d', 'lineColor': '#355c7d', 'secondaryColor': '#d6eef5', 'tertiaryColor': '#fff3cd', 'background': '#ffffff', 'mainBkg': '#e8f4f8', 'secondBkg': '#d6eef5', 'tertiaryBkg': '#fff3cd', 'clusterBkg': '#f9fbfd', 'clusterBorder': '#355c7d', 'edgeLabelBackground': '#ffffff' }}}%%
%%| fig-cap: "Inside a member function, this points to the current object"
%%| fig-width: 6
%%| fig-height: 2.8
flowchart LR
This["this : Point*"]
Obj["current object p"]
Member["p.x / p.y"]
This --> Obj --> Member
1.5 Entity Declarations in C/C++
A C++ program consists of a sequence of declarations, each introducing an entity. Entities include:
- Value: A literal constant (e.g.,
42,3.14) - Object: A named or unnamed region of memory containing a value
- Reference: A synonym (alias) for another object
- Function: A sequence of statements performing operations
- Type: A predefined or user-defined type (class)
1.5.1 References: Aliases for Objects
A reference is an alias (alternative name) for an existing object. Once initialized, a reference always refers to the same object and behaves exactly like that object:
int x = 10;
int& ref = x; // ref is an alias for x
ref = 20; // Changes x to 20
cout << x; // Prints 20
int* ptr = &x; // Pointer: stores the address of x
*ptr = 30; // Changes x to 30 (dereference ptr to access x)Key differences between references and pointers:
| Feature | Reference | Pointer |
|---|---|---|
| Syntax | int& ref = x; |
int* ptr = &x; |
| Must be initialized | Yes, immediately | No, can be nullptr |
| Can be reassigned | No, always refers to same object | Yes, can point to different objects |
| Can be null | No | Yes (nullptr) |
| Dereferencing | Automatic (transparent) | Manual (*ptr) |
| Memory address | &ref gives address of object |
ptr is the address |
Why references are used in constructors:
C(C& other) { } // Copy constructor with referenceIf we used pass-by-value instead:
C(C other) { } // WRONG! Infinite recursionThis would require copying other to pass it, which calls the copy constructor, which needs to copy, which calls the copy constructor, etc. - infinite recursion!
1.5.2 Variable Declaration Syntax
The general form of a variable declaration is:
S T name initializer;
Where:
- S: Storage class specifier (optional, e.g.,
static,extern) - T: Type specifier (required, e.g.,
int,double,Point) - name: The identifier for the variable (required)
- initializer: Specifies the initial value (optional)
- ; Delimiter (required)
Static semantics (compile-time): The declaration adds the name to the current scope, making it available to subsequent code.
Dynamic semantics (run-time): Memory is allocated, the initializer expression is evaluated (with type conversion if needed), and the value is stored.
1.6 Initialization Forms in C++
C++ provides multiple ways to initialize variables, which can be confusing but offers flexibility:
1.6.1 Traditional Initialization
int y = 0; // Assignment-style initialization
int x(0); // Functional-style initialization1.6.2 Uniform Initialization (C++11)
C++11 introduced braced initialization (also called uniform initialization) using curly braces {}:
int z{0}; // Braced initialization
int t = {0}; // Braced initialization with =This syntax was designed to work uniformly across all types and contexts. Key advantages:
Prevents narrowing conversions - catches potential data loss at compile time:
double x = 3.14, y = 2.71, z = 1.41; int sum2(x+y+z); // OK, but data loss (implicit narrowing) int sum3 = x+y+z; // OK, but data loss (implicit narrowing) int sum1{x+y+z}; // Error: narrowing conversion not allowedDistinguishes objects from function declarations (Most Vexing Parse problem):
class C { ... }; C c1(); // Function declaration (not an object!) C c2{}; // Object declaration with default initialization
Note on complexity: While the idea is uniform initialization, C++ actually has nineteen different initialization forms with subtle differences. For practical purposes, prefer braced initialization for its safety features.
1.7 Understanding Memory: Stack vs Heap
Before discussing object creation, you need to understand the two main memory regions in a C++ program:
1.7.1 Stack Memory
The stack is a region of memory that automatically grows and shrinks as functions are called and return:
void function() {
int x = 5; // Allocated on stack
double y = 3.14; // Allocated on stack
Point p; // Allocated on stack
} // All automatically deallocated hereStack characteristics:
- Fast: Very efficient allocation/deallocation
- Automatic management: No need to manually free memory
- Limited size: Typically a few megabytes (varies by system)
- LIFO (Last-In-First-Out): Objects destroyed in reverse creation order
- Scope-based lifetime: Objects live until end of their scope
Visual representation:
Function calls Stack memory
-------------- ------------
main() | | <- Top of stack
calls foo() | foo's x |
calls bar() | foo's y |
| bar's z | <- Current stack frame
|__________|
1.7.2 Heap Memory
The heap (also called free store) is a region for manually managed memory:
void function() {
int* ptr = new int(5); // Allocated on heap
Point* p = new Point(); // Allocated on heap
// Objects still exist here
delete ptr; // Manual deallocation
delete p; // Manual deallocation
} // Pointers destroyed, but if you forget 'delete', memory leaks!Heap characteristics:
- Slower: More complex allocation/deallocation
- Manual management: Must explicitly
deletewhat younew - Large size: Limited mainly by system RAM
- Flexible lifetime: Objects live until you explicitly destroy them
- Fragmentation risk: Can become fragmented over time
Memory leak example:
void badFunction() {
int* ptr = new int(5); // Allocates on heap
return; // Forgets to delete - MEMORY LEAK!
} // ptr variable destroyed, but the integer on heap remains forever1.7.3 Static vs Dynamic Object Creation
C++ offers two fundamentally different ways to create objects:
Static (Automatic) Declaration:
void foo() {
Test staticTest; // Created on stack
}Characteristics:
- Object is created by its declaration
- Memory allocated on the stack
- Accessed by its name
- Exists until end of scope (statically determined lifetime)
- Constructor called at declaration point
- Destructor called automatically when leaving scope
- Use when: Object lifetime matches scope, not too large
Dynamic Declaration:
void foo() {
Test* dynamicTest = new Test(); // Created on heap
delete dynamicTest; // Don't forget!
}Characteristics:
- Object created by explicit
newoperator - Memory allocated on the heap
- Object is unnamed; accessed via pointer
- Exists until explicit
deleteoperator (dynamically determined lifetime) - Constructor called by
new - Must manually call
deleteto invoke destructor and free memory - Use when: Object needs to outlive scope, size unknown at compile-time, or very large
Memory leakage occurs when dynamically allocated memory is never freed (no delete called). This is associated with heap memory only.
Complete lifecycle comparison:
void example() {
// STACK OBJECT
Test stack; // 1. Memory allocated
// 2. Constructor called
// Use stack...
} // 3. Destructor called
// 4. Memory automatically freed
void example2() {
// HEAP OBJECT
Test* heap = new Test(); // 1. Memory allocated
// 2. Constructor called
// Use heap...
delete heap; // 3. Destructor called
// 4. Memory manually freed
}1.8 Constructors
Constructors are special member functions that initialize objects when they are created. They have the same name as the class and no return type.
1.8.1 Types of Constructors
C++ recognizes four main types of constructors:
Default Constructor: Takes no arguments
class C { public: int a; C() : a{0} {} // Default constructor };Conversion Constructor: Takes a single argument of a different type
C(int i) : a(i) {} // Converts int to CCopy Constructor: Takes a reference to another object of the same class
C(C& other) { this->a = other.a; }Other Constructors: Multiple arguments or specific parameter combinations
C(int i, int j) { a = i + j; }
1.8.2 Member Initialization List
The preferred way to initialize member variables is using the member initialization list (after the colon):
Point() : x(0.0), y(0.0) { } // Preferred: initialization list
Point() { x = 0.0; y = 0.0; } // Works but less efficient: assignment in bodyWhy initialization lists are preferred:
- More efficient: Members are initialized directly, not default-constructed then assigned
- Required for: Const members, reference members, members without default constructors
- Clearer intent: Separates initialization from other constructor logic
1.8.3 Constructor Invocation
Different declaration syntaxes invoke different constructors:
class C {
C() { } // Default
C(int i) { } // Conversion
C(C& c) { } // Copy
C(int i, int j) { } // Other
};
C c1; // Default constructor
C c2(1); // Conversion constructor
C c3 = 1; // Conversion constructor (+ copy, often optimized away)
C c4 = C(1); // Conversion constructor (+ copy, often optimized away)
C c5(c2); // Copy constructor
C c6 = c2; // Copy constructor
C c7{1, 2}; // Constructor with 2 parameters
C c8(); // FUNCTION DECLARATION (not object!)Important: C c8(); is not an object declaration - it’s a function declaration returning C. This is called the Most Vexing Parse problem. Use C c8{}; for default construction.
1.8.4 Copy Elision and Compiler Optimization
Conceptually, statements like C c3 = 1; should:
- Call conversion constructor to create temporary
Cobject - Call copy constructor to initialize
c3from temporary
In practice, compilers are required to optimize this in many cases (since C++17, this is mandatory). The object is created directly without calling the copy constructor.
However: Even when the copy constructor isn’t called, it must still be accessible (not private). If it’s private, you’ll get a compile error even though it wouldn’t actually be used.
1.9 Destructors
A destructor is a special member function that cleans up when an object is destroyed. It has the class name preceded by a tilde (~) and takes no parameters.
class Test {
public:
int x;
Test() : x(0) {
cout << "Constructor called" << endl;
}
~Test() {
cout << "Destructor called" << endl;
}
};Purpose of destructors:
- Release resources the object has acquired (files, network connections, locks)
- Free dynamically allocated memory
- Perform cleanup operations
Automatic invocation:
- For stack objects: destructor called automatically at end of scope
- For heap objects: destructor called when
deleteis executed
Explicit destructor calls (rarely needed):
c.Test::~Test(); // Explicit call - object still exists after!
delete pc; // Correct way for heap objects - calls destructor and frees memory⚠️ Warning: Explicit destructor calls are rarely appropriate. For stack objects, the destructor will be called again automatically, leading to errors (double-deletion, etc.).
1.10 Function Overloading and Overload Resolution
Before discussing type conversions in depth, it’s important to understand function overloading - having multiple functions with the same name but different parameters.
Function Overloading Example:
void print(int x) {
cout << "Integer: " << x << endl;
}
void print(double x) {
cout << "Double: " << x << endl;
}
void print(const char* s) {
cout << "String: " << s << endl;
}
print(42); // Calls print(int)
print(3.14); // Calls print(double)
print("hello"); // Calls print(const char*)Overload Resolution is the process the compiler uses to determine which overloaded function to call:
- Find candidate functions: All functions with the matching name
- Find viable functions: Those that can be called with the given arguments (with or without conversions)
- Find the best match: Prefer exact matches over conversions, better conversions over worse ones
Example of Overload Resolution:
void foo(int x) { } // #1
void foo(double x) { } // #2
foo(5); // Calls #1: exact match for int
foo(5.0); // Calls #2: exact match for double
foo(5.5f); // Calls #2: float→double is better than float→intWhy This Matters:
Understanding overload resolution is crucial for:
- Using
deleteto prevent specific overloads - Understanding which constructor gets called
- Avoiding ambiguous function calls
- Writing efficient code (avoiding unnecessary copies)
1.11 Type Conversions
C++ supports both standard conversions (built into the language) and user-defined conversions (for classes).
1.11.1 Standard Conversions
Examples include:
- Array to pointer
- Integer to boolean
- Double to long integer
- Pointer to derived class to pointer to base class
These happen implicitly when needed:
void foo(double x) { ... }
foo(3.14); // OK: double literal
foo(3); // OK: int converted to double
foo(true); // OK: boolean converted to double (true → 1.0)1.11.2 Restricting Conversions with delete
To explicitly forbid unwanted conversions, declare overloads as deleted:
void foo(double x) { ... }
void foo(int) = delete;
void foo(bool) = delete;
foo(3.14); // OK
foo(3); // Error: deleted function
foo(true); // Error: deleted functionFor more comprehensive restriction, use a deleted template:
template<typename T>
void foo(T) = delete;
void foo(double x) { ... } // Only this overload allowed
foo(3.14); // OK: calls the double version
foo(3); // Error: would instantiate deleted template
foo(true); // Error: would instantiate deleted template1.12 Constant Expressions: const vs constexpr
1.12.1 const Qualifier
The const qualifier indicates a variable’s value shouldn’t change after initialization:
const int x = expression; // Value fixed after initializationHowever, const doesn’t guarantee compile-time evaluation - the expression can involve runtime computations.
1.12.2 constexpr Specifier (C++11)
The constexpr specifier guarantees a value can be computed at compile time:
constexpr int y = 42; // Must be evaluable at compile-timeKey points:
constexprimpliesconstfor objects- The value can be used in contexts requiring compile-time constants (array sizes, template arguments)
- Provides performance benefits - computation done once at compile time
Informal definition: A constant expression is an expression whose value can be calculated at compile time.
1.12.3 constexpr Functions
Functions can also be declared constexpr, allowing them to be used in constant expressions:
constexpr int Sqr(int arg) { return arg * arg; }
constexpr int s1 = Sqr(5); // OK: computes 25 at compile timeRequirements for constexpr functions:
- Must be non-virtual
- Body should contain a single return statement (C++11; relaxed in C++14)
- Arguments and return type must be literal types (scalar types or aggregates)
- For constructors, only initialization lists are allowed
Use case - template arguments:
template<int N>
class list { }
constexpr int sqr1(int arg) { return arg * arg; }
int sqr2(int arg) { return arg * arg; }
const int X = 2;
list<sqr1(X)> mylist1; // OK: sqr1 is constexpr
list<sqr2(X)> mylist2; // Error: sqr2 is not constexpr1.12.4 const and constexpr Together
Using both is redundant for simple objects since constexpr implies const:
constexpr const int N = 5; // Same as below
constexpr int N = 5; // const is implicitHowever, they can apply to different parts of a declaration:
static constexpr int N = 3;
constexpr const int* NP = &N;
// constexpr applies to pointer (NP is a constant pointer)
// const applies to pointee (*NP is constant data)1.13 Type Specification Simplification
Complex type specifications can be difficult to read and write:
int (*(a4[10]))(int);
// "a4 is an array of 10 pointers to functions taking int and returning int"1.13.1 typedef (C-style)
The typedef keyword creates a type alias:
typedef int (*PtrFun)(int);
PtrFun a4[10]; // Much clearer!1.13.2 using Declaration (Modern C++)
The using syntax is more readable and consistent:
using PtrFun = int (*)(int);
PtrFun a4[10];The using syntax is preferred in modern C++ because:
- More intuitive order (alias name on left)
- Works better with templates
- Consistent with other
usingstatements
1.14 Operator Functions
Operator functions allow defining how standard operators (+, -, *, [], etc.) work with user-defined types. This makes classes behave more like built-in types.
1.14.1 Basic Syntax
class Point {
double x, y;
public:
void operator+=(double v) {
x += v;
y += v;
}
};
Point p(1.5, 3.5);
p += 0.5; // Equivalent to: p.operator+=(0.5)The operator function is called when the corresponding operator is used with an object of that class.
1.14.2 Common Operators
class C {
int member;
public:
C operator+(const C& c1) { // Binary +
return C(member + c1.member);
}
int operator[](int p) { // Subscript
return member - p;
}
int operator()(int p) { // Function call
return member + p;
}
C& operator=(const C& other) { // Assignment
member = other.member;
return *this;
}
};
C c1, c2;
C sum = c1 + c2; // Calls operator+
int value = sum[1]; // Calls operator[]
int result = sum(3); // Calls operator()
c1 = c2; // Calls operator=1.14.3 Rules for Operator Overloading
- Arity (number of operands) cannot change:
+must stay binary,!must stay unary - Precedence cannot change:
*will always bind tighter than+ - Cannot create new operators: Can only overload existing ones
- Most operators can be overloaded: Including
+,-,*,/,[],(),new,delete - Some operators cannot be overloaded:
.,::,.*,?:,sizeof
1.15 Conversion Functions
Conversion functions define how to convert a class object to another type. They enable user-defined types to behave like built-in types in contexts requiring type conversion.
1.15.1 Basic Syntax
class C {
int member;
public:
operator bool() { // Conversion to bool
return member != 0;
}
};
C c1(1);
if (c1) { // Equivalent to: if (c1.operator bool())
// Do something
}Syntax rules:
- Name is
operator TargetType() - No return type specified (it’s implicit - must return
TargetType) - No parameters
- Empty parentheses still required
1.15.2 Conversion Constructors vs Conversion Functions
These provide opposite conversion directions:
class C {
int value;
public:
// Conversion constructor: int → C
C(int i) : value(i) { }
// Conversion function: C → bool
operator bool() { return value != 0; }
};
C c = 5; // Uses conversion constructor (int → C)
if (c) { } // Uses conversion function (C → bool)1.15.3 Conversion Ambiguity
Ambiguity arises when multiple conversion paths exist:
class B;
class A {
public:
A(B& b) { } // Conversion constructor: B → A
};
class B {
public:
operator A() { } // Conversion function: B → A
};
B b;
A a = b; // Error: Ambiguous! Use A(b) or b.operator A()?Resolution: Be careful when defining conversions. Consider making constructors explicit to prevent implicit conversions.
1.16 Making Classes Behave Like Fundamental Types
One of C++’s design goals is enabling user-defined types to behave like built-in types. Through the mechanisms covered above, we can achieve this:
1. Initialization: Both support similar syntax
int i(1); // Built-in type
C c(1); // User-defined type (conversion constructor)
C c1(c); // Copy initialization2. Assignment: Can define assignment semantics
i = 7; // Built-in assignment
c = 7; // User-defined assignment (via conversion + assignment operator)
c1 = c2; // User-defined copy assignment3. Expressions: Can participate in arithmetic and other operations
i = k + m; // Built-in operator
c = c1 + c2; // User-defined operator+4. Type Conversions: Can convert to other types in expressions
if (i) { } // Standard conversion int → bool
if (c) { } // User-defined conversion C → boolBy providing appropriate constructors, operator functions, and conversion functions, a class can integrate seamlessly with C++’s type system and behave consistently with fundamental types.
1.17 Code Style
Following consistent coding standards improves readability and maintainability. For this course:
- Use Qt’s coding style for C++ code
- CLion setup: Settings → Editor → Code Style → C/C++ → Set from… → Choose “Qt”
- Format code regularly: Learn the keyboard shortcut for your OS
- Use the auto-format feature before submitting assignments
Consistent style makes code easier to review and collaborate on.
2. Definitions
- Class: A user-defined compound type that groups data members and member functions together.
- Object: An instance of a class; a region of memory with a specific type and value.
- Member Variable (Data Member): A variable declared inside a class that represents part of the object’s state.
- Member Function (Method): A function declared inside a class that operates on the object’s data.
- Constructor: A special member function that initializes an object when it is created. Has the same name as the class and no return type.
- Default Constructor: A constructor that takes no parameters and provides default initialization.
- Conversion Constructor: A constructor that takes a single parameter of a different type, enabling implicit or explicit conversion to the class type.
- Copy Constructor: A constructor that takes a reference to another object of the same class and creates a copy.
- Destructor: A special member function (named
~ClassName) that performs cleanup when an object is destroyed. - Access Specifier: Keywords (
public,private,protected) that control the visibility and accessibility of class members. - Private Members: Class members accessible only from within the class itself.
- Public Members: Class members accessible from any code that has access to an object of the class.
- Encapsulation: The practice of hiding implementation details (private data) while exposing a controlled interface (public functions).
- Static Declaration: Creating an object on the stack with automatic lifetime (exists until end of scope).
- Dynamic Declaration: Creating an object on the heap using
new, with manual lifetime management requiringdelete. - Stack Memory: Memory used for static (automatic) objects; automatically managed, fast, limited size.
- Heap Memory: Memory used for dynamic objects; manually managed, slower, larger size.
- Memory Leakage: Failure to free dynamically allocated memory, causing the program to consume increasing memory over time.
- Scope: The region of code where a name is visible and can be used (compile-time concept).
- Lifetime: The period during program execution when an object exists in memory (runtime concept).
- Entity: A fundamental program element in C++ (value, object, reference, function, type, template, etc.).
- Declaration: A statement that introduces an entity and makes its name available in the current scope.
- Initialization: The process of giving an object its initial value when it is created.
- Assignment: Giving an already-existing object a new value, replacing its previous value.
- Reference: An alias (alternative name) for an existing object; must be initialized and cannot be rebound.
- Pointer: A variable that stores the memory address of another object; can be null, reassigned, and requires explicit dereferencing.
- Uniform Initialization: Braced initialization syntax (
{}) introduced in C++11 that works consistently across types. - Narrowing Conversion: A type conversion that may lose information (e.g., double to int), prevented by braced initialization.
- Most Vexing Parse: A C++ ambiguity where
T obj();is parsed as a function declaration, not an object declaration. - Member Initialization List: The syntax after a constructor’s parameter list (
:) for initializing members before the constructor body executes. - Copy Elision: Compiler optimization that eliminates unnecessary copy operations, required by C++ standard in many cases.
thisPointer: An implicit pointer available in member functions that points to the object on which the member function is called.- Function Overloading: Having multiple functions with the same name but different parameter lists.
- Overload Resolution: The compiler process of determining which overloaded function to call based on argument types.
- Type Conversion: Transforming a value from one type to another, either implicitly or explicitly.
- Standard Conversion: Built-in type conversions defined by the C++ language (int to double, array to pointer, etc.).
- User-Defined Conversion: Conversions for class types defined through conversion constructors or conversion functions.
constQualifier: Specifies that a variable’s value cannot be modified after initialization.constexprSpecifier: Specifies that a value or function can be evaluated at compile time, guaranteeing compile-time constant behavior.- Constant Expression: An expression whose value can be computed at compile time.
- Literal Type: A type that can be used in
constexprcontexts (scalar types, simple aggregates). typedef: C-style keyword for creating type aliases.usingDeclaration: Modern C++ syntax for creating type aliases (preferred overtypedef).- Operator Function: A special member function that defines behavior for a standard operator when applied to class objects.
- Operator Overloading: Defining custom behavior for operators when used with user-defined types.
- Conversion Function: A special member function (named
operator TargetType()) that defines how to convert a class object to another type. deleteSpecifier: Keyword used to explicitly prohibit a function from being called (e.g., to prevent unwanted conversions or copying).- Template: A blueprint for creating generic functions or classes that work with multiple types.
3. Examples
3.1. Create a Box Class with Basic Constructors (Lab 2, Task 1)
Write a program that contains a class Box.
- Box should have the length, width, and height as member variables. The variables should be of type
unsigned int. - Box should have three constructors: default, copy, conversion.
- Box should have the assignment operator.
Click to see the solution
Key Concept: Implement essential constructors and operators to make the class behave like a fundamental type.
#include <iostream>
class Box
{
private:
unsigned int length;
unsigned int width;
unsigned int height;
public:
// Default constructor - initializes with zeros
Box() : length(0), width(0), height(0) {
std::cout << "Default constructor called" << std::endl;
}
// Conversion constructor - creates a cube from one dimension
Box(unsigned int side) : length(side), width(side), height(side) {
std::cout << "Conversion constructor called" << std::endl;
}
// Copy constructor - creates a copy of another box
Box(const Box& other)
: length(other.length), width(other.width), height(other.height) {
std::cout << "Copy constructor called" << std::endl;
}
// Assignment operator - assigns values from one box to another
Box& operator=(const Box& other) {
if (this != &other) { // Check for self-assignment
length = other.length;
width = other.width;
height = other.height;
}
std::cout << "Assignment operator called" << std::endl;
return *this;
}
// Getters for testing
unsigned int getLength() const { return length; }
unsigned int getWidth() const { return width; }
unsigned int getHeight() const { return height; }
};
int main() {
Box b1; // Default constructor
Box b2(5); // Conversion constructor (5x5x5 cube)
Box b3(b2); // Copy constructor
Box b4;
b4 = b2; // Assignment operator
return 0;
}Implementation Notes:
- Default Constructor: Uses member initialization list to set all dimensions to 0
- Conversion Constructor: Takes a single
unsigned intto create a cube - Copy Constructor: Takes a const reference to avoid infinite recursion and unnecessary copying
- Assignment Operator:
- Returns a reference to allow chaining (
b1 = b2 = b3) - Checks for self-assignment to avoid unnecessary work
- Returns
*this(the current object)
- Returns a reference to allow chaining (
Answer: Complete Box class implementation with default, conversion, copy constructors and assignment operator.
3.2. Array of Pointers to Functions Using using (Tutorial 2, Task 1)
Using a modern using declaration (instead of the legacy typedef), define the type “array of 10 pointers to functions” that take an int argument and return int.
The legacy typedef equivalent is:
typedef int (*PtrFun)(int);
PtrFun a4[10];Rewrite this so that a single using declaration defines the complete array type directly (e.g., using MyType = ...;).
Click to see the solution
Key Concept: In C++11 and later, using aliases support function pointer and array types directly, making complex declarations more readable than typedef.
A pointer to a function int(int) is written as int(*)(int). An array of 10 such pointers is int(*[10])(int). The using declaration wraps this cleanly:
using FuncPtrArray = int(*[10])(int);Usage example:
#include <iostream>
int double_it(int x) { return x * 2; }
int triple_it(int x) { return x * 3; }
int main() {
using FuncPtrArray = int(*[10])(int);
FuncPtrArray funcs; // array of 10 function pointers
funcs[0] = double_it;
funcs[1] = triple_it;
std::cout << funcs[0](5) << "\n"; // 10
std::cout << funcs[1](5) << "\n"; // 15
return 0;
}Comparison with typedef:
| Style | Declaration |
|---|---|
typedef (legacy) |
typedef int (*PtrFun)(int); PtrFun a4[10]; |
using (modern) |
using FuncPtrArray = int(*[10])(int); |
The using form is preferred in modern C++ because the alias name appears on the left and the type on the right, matching the natural reading direction.
Answer: using FuncPtrArray = int(*[10])(int);